package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * @Author xushupeng
 * @Date 2024-12-27 21:01
 */

@Component
@Aspect
@Slf4j
public class AuotFillAspect {
//    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
//    public void autoFillPointCut(){}

    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    //对于有些表 具有 updateTime createTime ...等字段 我们可以在crud方法调用之前,将实体类的属性改变
    //所以需要使用到 @Before
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段自动填充...");
        //在Spring AOP（面向切面编程）中，JoinPoint是一个关键的概念，它代表程序执行过程中的某个特定点，比如方法的调用或异常的抛出

        // 获取到当前被拦截的方法上的数据库操作类型 注解的作用其实就是表示当前方法属于那种操作类型 比如insert update
        // oinPoint.getSignature()方法可以获取到一个Signature对象，
        // 这个对象包含了方法的名称、声明类型（如public、private等）、所属类的类名等信息。如果连接点是一个方法
        // ，可以将Signature对象转换为MethodSignature对象，这样就能获取到更详细的方法签名信息，例如方法的返回类型和参数类型。

        //1.获取到当前被拦截的方法上的数据库操作类型(这就是注解的作用 起一个标识作用)
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();//方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库操作类型

        // 获得当前被拦截的方法的参数--实体对象 我们需要改变实体对象上面的属性值 updateTime createTime updateUser...

//        getArgs() 方法
//        getArgs()：这是 JoinPoint 接口中的一个方法，用于获取当前连接点（即被拦截的方法）被调用时的参数。
//        返回值是一个 Object[] 数组，数组中的每个元素对应于被调用方法的一个参数。
//        使用场景
//        在实际使用中，getArgs() 方法可以帮助我们获取被拦截方法的实际参数，以便在切面中进行处理。比如，记录日志、参数校验或进行特定的业务逻辑处理。
        Object[] args = joinPoint.getArgs();
        if(args==null||args.length==0){
            return ;
        }

        Object entity = args[0]; //当前需要修改的实体类 因为不知道是什么类型（增加或修改哪一个数据表的值 所以直接向上转型）

        //准备赋值数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        // 根据不同的操作类型，对对应的属性通过反射进行赋值
        if (operationType == OperationType.INSERT) { //只有插入方法才有 setCreateTime setCreateUser

            // 或许会问 为什么不直接将entity向下转型，直接使用set方法来设置值
            // 还是因为当前需要修改的实体类 不知道是什么类型（不能预先知道是哪个表） 但是字段名 和方法名是知道的 所以使用反射赋值

            try {
            //getDeclaredMethod(String name, Class<?>... parameterTypes)：此方法用于获取指定名称和参数类型的方法对象。
            //name：方法的名称，通常是字符串形式。
            //parameterTypes：方法的参数类型，使用 Class 对象数组指定。如果方法没有参数，则可以传递空数组。
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 通过反射为对象赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);

            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (operationType == OperationType.UPDATE) { //更新方法
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }


        }
    }


}
